﻿using System;
using System.Net;
using System.Runtime.DurableInstancing;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Web.Routing;
using MvcFlow.Diagnostics;
using MvcFlow.Execution;
using MvcFlow.Mvc;
using MvcFlow.Mvc.ActionResults;

namespace MvcFlow.Runtimes
{
    public class AsyncWorkflowRuntime : IWorkflowRuntime
    {
        private static readonly ILog                log = LogManager.GetLog("Runtime");

        private readonly AsyncWorkflowApplication   workflowApplication;
        private readonly IWorkflowStateStore        workflowStateStore;
        private readonly IWorkflowInstanceProvider  workflowInstanceProvider;
        private readonly IWorkflowHost              workflowHost;

        public async Task<IActionResult> ExecuteStep(RequestContext requestContext, string workflowName)
        {
            var workflow        = await workflowInstanceProvider.GetWorkflowInstance(requestContext,workflowName);
            var existingState   = await workflowStateStore.Get(requestContext,workflowName);

            if (existingState != null)
            {
                log.Debug("Found existing state for instance '{0}' of '{1}'",existingState.InstanceId,existingState.Name);
            }
            else
            {
                log.Debug("No existing state for workflow '{0}'",workflowName);
            }

            if (workflow != null)
            {
                var instanceId      = existingState != null ? existingState.InstanceId : (Guid?) null;
                var bookmarkName    = existingState != null ? existingState.BookmarkName : null;

                var request         = new HttpRequestData(requestContext.HttpContext.Request, requestContext.RouteData.Values);

                try
                {
                    var result      = await workflowApplication.ExecuteStepAsync(workflow, request, instanceId, bookmarkName);
                    var resultState = new WorkflowState(workflowName, result.InstanceId, result.BookmarkName);

                    if (resultState.ShouldContinue())
                    {
                        await workflowStateStore.Put(requestContext, resultState);
                    }
                    else
                    {
                        await workflowStateStore.Delete(requestContext, existingState);
                    }

                    if (result.ReturnValue != null)
                    {
                        var actionResult = result.ReturnValue as IActionResult;
                        return actionResult ?? CreateError(requestContext,HttpStatusCode.InternalServerError, "Return value is not convertible to IActionResult");
                    }

                    return CreateError(requestContext,HttpStatusCode.InternalServerError,"No return value was provided");
                }
                catch( AggregateException ex )
                {
                    return CreateError(requestContext,"Unknown workflow error", ex);
                }
            }

            return CreateError(requestContext,HttpStatusCode.NotFound, "Workflow '{0}' not found", workflowName);
        }

        private HttpStatusActionResult CreateError(RequestContext requestContext,string message,Exception ex)
        {
            log.ErrorException(message, ex);
            return HttpStatusActionResult.FromException(ex,workflowHost.IsDebugging(requestContext));
        }

        private HttpStatusActionResult CreateError(RequestContext requestContext,HttpStatusCode httpStatusCode,string message, params object[] parameters)
        {
            log.Error(message, parameters);
            return HttpStatusActionResult.FromError(string.Format(message, parameters), workflowHost.IsDebugging(requestContext));
        }

        public AsyncWorkflowRuntime(IWorkflowStateStore workflowStateStore,IWorkflowInstanceProvider workflowInstanceProvider,IWorkflowHost workflowHost,InstanceStore instanceStore)
        {
            this.workflowStateStore         = workflowStateStore;
            this.workflowInstanceProvider   = workflowInstanceProvider;
            this.workflowHost               = workflowHost;

            this.workflowApplication = new AsyncWorkflowApplication(instanceStore,extensions =>
            {
                extensions.Add(DependencyResolver.Current);                                                                      
            },
            () => SynchronizationContext.Current,
            "InputHttpRequest",
            "OutputActionResult");
        }
    }
}